package metachain

import (
	"math/big"
	"sync"
)

type epochEconomicsStatistics struct {
	numberOfBlocks                   uint64
	numberOfBlocksPerShard           map[uint32]uint64
	leaderFees                       *big.Int
	rewardsToBeDistributed           *big.Int
	rewardsToBeDistributedForBlocks  *big.Int // without leader fees, protocol sustainability and developer fees
	rewardsForProtocolSustainability *big.Int
	rewardsForEcosystemGrowth        *big.Int
	rewardsForGrowthDividend         *big.Int
	mutEconomicsStatistics           sync.RWMutex
}

// NewEpochEconomicsStatistics creates the end of epoch economics statistics
func NewEpochEconomicsStatistics() *epochEconomicsStatistics {
	return &epochEconomicsStatistics{
		numberOfBlocksPerShard:           make(map[uint32]uint64),
		leaderFees:                       big.NewInt(0),
		rewardsToBeDistributed:           big.NewInt(0),
		rewardsToBeDistributedForBlocks:  big.NewInt(0),
		rewardsForProtocolSustainability: big.NewInt(0),
		rewardsForEcosystemGrowth:        big.NewInt(0),
		rewardsForGrowthDividend:         big.NewInt(0),
	}
}

// Clean clears the previous data
func (es *epochEconomicsStatistics) Clean() {
	es.numberOfBlocksPerShard = make(map[uint32]uint64)
	es.leaderFees = big.NewInt(0)
	es.rewardsToBeDistributed = big.NewInt(0)
	es.rewardsToBeDistributedForBlocks = big.NewInt(0)
	es.rewardsForProtocolSustainability = big.NewInt(0)
	es.rewardsForEcosystemGrowth = big.NewInt(0)
	es.rewardsForGrowthDividend = big.NewInt(0)
}

// SetNumberOfBlocks sets the number of blocks produced in the epoch
func (es *epochEconomicsStatistics) SetNumberOfBlocks(nbBlocks uint64) {
	es.mutEconomicsStatistics.Lock()
	defer es.mutEconomicsStatistics.Unlock()
	es.numberOfBlocks = nbBlocks
}

// SetNumberOfBlocksPerShard sets the number of blocks per each shard produced in the epoch
func (es *epochEconomicsStatistics) SetNumberOfBlocksPerShard(blocksPerShard map[uint32]uint64) {
	es.mutEconomicsStatistics.Lock()
	defer es.mutEconomicsStatistics.Unlock()
	es.numberOfBlocks = 0

	es.numberOfBlocksPerShard = make(map[uint32]uint64)
	for k, v := range blocksPerShard {
		es.numberOfBlocksPerShard[k] = v
		es.numberOfBlocks += v
	}
}

// SetLeadersFees sets the leader fees for the previous epoch
func (es *epochEconomicsStatistics) SetLeadersFees(fees *big.Int) {
	es.mutEconomicsStatistics.Lock()
	defer es.mutEconomicsStatistics.Unlock()

	es.leaderFees = fees
}

// SetRewardsToBeDistributed sets the rewards to be distributed at the end of the epoch (includes the rewards per block,
// the block producers fees, protocol sustainability rewards and developer fees)
func (es *epochEconomicsStatistics) SetRewardsToBeDistributed(rewards *big.Int) {
	es.mutEconomicsStatistics.Lock()
	defer es.mutEconomicsStatistics.Unlock()

	es.rewardsToBeDistributed = big.NewInt(0).Set(rewards)
}

// SetRewardsToBeDistributedForBlocks sets the rewards to be distributed at the ed of the epoch for produced blocks
func (es *epochEconomicsStatistics) SetRewardsToBeDistributedForBlocks(rewards *big.Int) {
	es.mutEconomicsStatistics.Lock()
	defer es.mutEconomicsStatistics.Unlock()

	es.rewardsToBeDistributedForBlocks = big.NewInt(0).Set(rewards)
}

// NumberOfBlocks returns the number of blocks produced in the epoch
func (es *epochEconomicsStatistics) NumberOfBlocks() uint64 {
	es.mutEconomicsStatistics.RLock()
	defer es.mutEconomicsStatistics.RUnlock()

	return es.numberOfBlocks
}

// NumberOfBlocksPerShard returns the number of blocks produced in each shard in the epoch
func (es *epochEconomicsStatistics) NumberOfBlocksPerShard() map[uint32]uint64 {
	es.mutEconomicsStatistics.RLock()
	defer es.mutEconomicsStatistics.RUnlock()

	retMap := make(map[uint32]uint64)
	for k, v := range es.numberOfBlocksPerShard {
		retMap[k] = v
	}

	return retMap
}

// LeaderFees returns the leader fees given in the previous epoch
func (es *epochEconomicsStatistics) LeaderFees() *big.Int {
	es.mutEconomicsStatistics.RLock()
	defer es.mutEconomicsStatistics.RUnlock()

	return big.NewInt(0).Set(es.leaderFees)
}

// RewardsToBeDistributed returns the rewards to be distributed at the end of epoch (includes rewards for produced
// blocks, protocol sustainability rewards, block producer fees and developer fees)
func (es *epochEconomicsStatistics) RewardsToBeDistributed() *big.Int {
	es.mutEconomicsStatistics.RLock()
	defer es.mutEconomicsStatistics.RUnlock()

	return big.NewInt(0).Set(es.rewardsToBeDistributed)
}

// RewardsToBeDistributedForBlocks returns the rewards to be distributed at the end of epoch for produced blocks
func (es *epochEconomicsStatistics) RewardsToBeDistributedForBlocks() *big.Int {
	es.mutEconomicsStatistics.RLock()
	defer es.mutEconomicsStatistics.RUnlock()

	return big.NewInt(0).Set(es.rewardsToBeDistributedForBlocks)
}

// SetRewardsForProtocolSustainability sets the rewards for protocol sustainability
func (es *epochEconomicsStatistics) SetRewardsForProtocolSustainability(rewards *big.Int) {
	es.mutEconomicsStatistics.Lock()
	defer es.mutEconomicsStatistics.Unlock()

	es.rewardsForProtocolSustainability = big.NewInt(0).Set(rewards)
}

// SetRewardsForEcosystemGrowth sets the rewards for ecosystem growth
func (es *epochEconomicsStatistics) SetRewardsForEcosystemGrowth(rewards *big.Int) {
	es.mutEconomicsStatistics.Lock()
	defer es.mutEconomicsStatistics.Unlock()

	es.rewardsForEcosystemGrowth = big.NewInt(0).Set(rewards)
}

// SetRewardsForGrowthDividend sets the rewards for growth dividend
func (es *epochEconomicsStatistics) SetRewardsForGrowthDividend(rewards *big.Int) {
	es.mutEconomicsStatistics.Lock()
	defer es.mutEconomicsStatistics.Unlock()

	es.rewardsForGrowthDividend = big.NewInt(0).Set(rewards)
}

// RewardsForProtocolSustainability returns the rewards for protocol sustainability
func (es *epochEconomicsStatistics) RewardsForProtocolSustainability() *big.Int {
	es.mutEconomicsStatistics.RLock()
	defer es.mutEconomicsStatistics.RUnlock()

	return big.NewInt(0).Set(es.rewardsForProtocolSustainability)
}

// RewardsForEcosystemGrowth returns the rewards for ecosystem growth
func (es *epochEconomicsStatistics) RewardsForEcosystemGrowth() *big.Int {
	es.mutEconomicsStatistics.RLock()
	defer es.mutEconomicsStatistics.RUnlock()

	return big.NewInt(0).Set(es.rewardsForEcosystemGrowth)
}

// RewardsForGrowthDividend returns the rewards for growth dividend
func (es *epochEconomicsStatistics) RewardsForGrowthDividend() *big.Int {
	es.mutEconomicsStatistics.RLock()
	defer es.mutEconomicsStatistics.RUnlock()

	return big.NewInt(0).Set(es.rewardsForGrowthDividend)
}

func (es *epochEconomicsStatistics) RewardsForAccelerator() *big.Int {
	es.mutEconomicsStatistics.RLock()
	defer es.mutEconomicsStatistics.RUnlock()

	accumulatedValue := big.NewInt(0).Add(es.rewardsForGrowthDividend, es.rewardsForEcosystemGrowth)
	accumulatedValue = big.NewInt(0).Add(accumulatedValue, es.rewardsForProtocolSustainability)
	return accumulatedValue
}

// IsInterfaceNil returns nil if the underlying object is nil
func (es *epochEconomicsStatistics) IsInterfaceNil() bool {
	return es == nil
}
